package pers.chao.cacheable.core.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.springframework.core.annotation.Order;
import pers.chao.cacheable.core.annotation.RedisStruct;
import pers.chao.cacheable.core.annotation.Cache;
import pers.chao.cacheable.core.AbstractCacheProcessor;
import pers.chao.cacheable.core.factory.ICacheProcessorFactory;
import pers.chao.cacheable.infrastructure.ErrorDesc;
import pers.chao.cacheable.core.CacheableException;
import pers.chao.tool.aspect.AopUtil;
import pers.chao.tool.infrastructure.log.BaseLogFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;

/**
 * Description: 缓存切面
 *
 * @author W.Chao
 * @date 2020/9/20 18:12
 **/
@Aspect
@Order(3)
public class CacheableAspect {
	private static final Logger logger = BaseLogFactory.getLogger(CacheableAspect.class);

	private ICacheProcessorFactory factory;

	public CacheableAspect(ICacheProcessorFactory factory) {
		this.factory = factory;
	}

	@Pointcut("@annotation(pers.chao.cacheable.core.annotation.Cache)")
	public void cache() {
	}

	@Pointcut("@annotation(pers.chao.cacheable.core.annotation.CacheValue)")
	public void cacheValue() {
	}

	@Pointcut("@annotation(pers.chao.cacheable.core.annotation.CacheMap)")
	public void cacheMap() {
	}

	@Pointcut("@annotation(pers.chao.cacheable.core.annotation.CacheList)")
	public void cacheList() {
	}

	@Pointcut("@annotation(pers.chao.cacheable.core.annotation.CacheSet)")
	public void cacheSet() {
	}

	@Pointcut("@annotation(pers.chao.cacheable.core.annotation.CacheZSet)")
	public void cacheZSet() {
	}

	@Pointcut("@annotation(pers.chao.cacheable.core.annotation.CacheBitmap)")
	public void cacheBitmap() {
	}

	@Around("cache() && (cacheValue() || cacheMap() || cacheList() || cacheSet() || cacheZSet() || cacheBitmap())")
	public Object doAround(ProceedingJoinPoint joinPoint) {
		// 存储接口返回值
		Object result;

		try {
			// 获取请求方法参数
			Map<String, Object> params = AopUtil.getMethodArgsMap(joinPoint);
			Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
			// 获取@Cache注解
			Cache cache = method.getDeclaredAnnotation(Cache.class);
			/**
			 * 过滤出方法上五个redis结构注解中的其中一个，省去重复代码
			 * 若同时使用了多个结构注解，那么只能获取排序后的第一个
			 * warning !!! : 同时使用多个排序后可能使用的是不需要的那个
			 */
			Annotation structAnnotation = Arrays.stream(method.getDeclaredAnnotations())
					.filter((anno) -> null != AopUtil.findAnnotatedAnnotation(method, anno.annotationType(), RedisStruct.class))
					.sorted()
					.findFirst()
					.orElse(null);
			if (structAnnotation == null) {
				throw new CacheableException(ErrorDesc.ANNOTATION_USING_ERROR);
			}
			// 获取缓存执行器
			AbstractCacheProcessor cacheProcessor = factory.get(method, structAnnotation);
			// 方法执行前解析并执行缓存查询
			Object cacheResult = cacheProcessor.preProcess(cache, params, method);

			// 返回缓存值的条件
			// 1.判断是否是缓存清除方法: false为不是清除方法
			// 2.判断缓存策略: 不缓存或有key不更新的策略时不执行后续方法
			// 3.新加：更新模式(即使查询到缓存也不直接返回缓存结果)
			if (cacheResult != null && !cacheProcessor.isEvict()
					&& cacheProcessor.needCaching() && !cacheProcessor.isUpdate()) {
				logger.info("Cache Hit! Content: {}", cacheResult);
				return cacheResult;
			}

			// 执行接口调用的方法并获取返回值
			result = joinPoint.proceed();
			// 返回类型为void或为null的对象，无论什么条件都不缓存
			if (result != null) {
				// 更新模式则执行删除、局部更新、全更新操作
				// 缓存查询结果为null时代表第一次查询，需要将返回结果缓存起来
				if (cacheProcessor.needCaching() && (cacheResult == null || cacheProcessor.isUpdate())) {
					cacheProcessor.process(result);
				}
			}

			// 末尾操作，各数据结构可以自行实现覆盖
			cacheProcessor.finish();

		} catch (Throwable e) {
			throw new CacheableException(e);
		}

		return result;
	}

}
